#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>

#include <map>
#include <vector>
#include <iostream>

#include "tty_io.hpp"
#include "helper.hpp"

int gsm_fd = -1;

extern std::map<std::string, Serial_info> port_map;

b_packet_t* send_request(const std::string port, b_packet_t* req)
{	
    int c, s, rv;
    fd_set readfds;
    b_packet_t* resp;
    int fd = port_map[port].s_fd;
    struct timeval timeout = { .tv_sec = 0, .tv_usec = 500000 };

    /* lock the corresponding mutex for I/O. */
    if(pthread_mutex_lock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_lock");
        return NULL;
    }

    do {
        s = sizeof(b_packet_t) + req->size;
        c = write(fd, req, s);

        if(c != s){
            logger("ERROR: unable to send req [%d %d %d]\n", 
                    req->s_code,
                    req->addr,
                    req->size);
            perror("write");
            resp = NULL;
            break;
        }

        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        rv = select(fd + 1, &readfds, NULL, NULL, &timeout);

        if(rv == -1) {
            perror("select");
            resp = NULL;
            break;
        } else if(rv == 0){
            logger("ERROR: failed to read between specified timeout.\n");
            resp = NULL;
            break;
        }

        resp = (b_packet_t*)malloc(sizeof(b_packet_t));
        assert(resp != NULL);

        char *ptr = (char*)resp;
        c = 0;

        while(read(fd, ptr, sizeof(char)) == 1) {
            if(ptr == (char*)&resp->size) {
                resp = (b_packet_t*)realloc(resp, sizeof(b_packet_t) + resp->size);
                assert(resp != NULL);
                ptr = (char*)&resp->size;
                assert(resp->size > 0);
                c = resp->size;
            }
            if(ptr > (char*)&resp->size && c == 0) 
                break;
            ptr++; c--;
        }

        /* checksum. */
        assert(resp->size > 0);
        if((resp->data[resp->size - 1]) != calculate_csum(resp)){
            logger("ERROR: checksum is not correct.\n");
        }
    } 
    while(0);

    FD_ZERO(&readfds);

    if(pthread_mutex_unlock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_unlock");
    }

    return resp;
}

bool read_from_device(const std::string port, const std::string str_data,
        std::vector<uint8_t>& resp)
{	
    //TODO
    resp.push_back(3);
    resp.push_back(1);
    resp.push_back(2);
    resp.push_back(3);
    resp.push_back(4);
    resp.push_back(5);
    resp.push_back(6);
    resp.push_back(7);
    resp.push_back(8);
    resp.push_back(2);
    resp.push_back(2);
    resp.push_back(11);
    resp.push_back(1);
    resp.push_back(1);
    resp.push_back(9);
    resp.push_back(8);
    resp.push_back(8);
    resp.push_back(15);
    resp.push_back(13);
    return true;

    bool rv = true;
    int data_size, write_count = 0, fd = -1;
    unsigned char *data_dec;
    std::vector<std::string> tokens;

    std::map<std::string, Serial_info>::iterator p_it;
    p_it = port_map.find(port);
    if(p_it == port_map.end()){
        logger("wrong port specified: [%s]\n", port.c_str());
        return false;
    }

    fd = port_map[port].s_fd;

    /* lock the corresponding mutex for I/O. */
    if(pthread_mutex_lock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_lock");
        return false;
    }

    /* tokenize the data_dec. */
    tokenize(str_data, " ", tokens);

    data_size = tokens.size() + 1;
    data_dec = new unsigned char[data_size];

    for(size_t i = 0; i < tokens.size(); ++i){
        data_dec[i] = atoi(tokens[i].c_str());
    }

    /* Calculating the checksum. */
    data_dec[data_size-1] = 0;
    for(int i = 0; i < data_size - 1; ++i){
        data_dec[data_size-1] += data_dec[i];
    }

    data_dec[data_size - 1] = 256 - (data_dec[data_size - 1] % 256);

    struct timeval timeout = { .tv_sec = 0, .tv_usec = 500000 };
    fd_set readfds;

    do {
        write_count = write(fd, data_dec, data_size);

        if(write_count != data_size){
            logger("unable to write: data_dec = [%s %d] ...\n",
                    str_data.c_str(), data_dec[data_size-1]);
            rv = false;
            break;
        }

        int rv_select;

        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        rv_select = select(fd + 1, &readfds, NULL, NULL, &timeout);

        if(rv_select == -1) {
            perror("select");
            rv = false;
            break;
        } else if(rv_select == 0){
            logger("ERROR: FAILED TO READ BETWEEN SPECIFIED TIMEOUT.\n");
            rv = false;
            break;
        }

        if(!FD_ISSET(fd, &readfds)){
            logger("ERROR: SOMETHING IS WRONG WITH SELECT.\n");
            rv = false;
            break;
        }

        size_t bytes_must_read = 0, bytes_read = 0, n = 0;
        uint8_t resp_value;

        while(read(fd, &resp_value, sizeof(resp_value)) == 1){
            if(n == 2){
                bytes_must_read = resp_value;
            } else if(n > 2 && bytes_read == bytes_must_read + 2){
                resp.push_back(resp_value);
                break;
            }   
            n++;
            bytes_read++;
            resp.push_back(resp_value);
        }

        /* checksum. */
        int sum_of_all_bytes = 0;
        for(size_t i = 0; i < resp.size(); ++i){
            sum_of_all_bytes += resp[i];
        }

        if((sum_of_all_bytes % 256) != 0){
            logger("ERROR: checksum is not correct.\n");
            rv = false;
            print_resp(resp);
        }

    } 
    while(0);

    FD_ZERO(&readfds);

    if(data_dec){
        delete[] data_dec;
    }

    if(pthread_mutex_unlock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_unlock");
    }

    return rv;
}

int init_tty(const std::string port)
{
    struct termios termio;
    int fd, r;

    memset (&termio, 0, sizeof (termio));

    if ((fd = open (port.c_str(), O_RDWR | O_NDELAY | O_NOCTTY)) == -1) {
        std::cerr << "Unable to open " << port << std::endl;
        exit(-1);
    }

    fcntl(fd, F_SETFL, 0);
    r = tcgetattr(fd, &termio);

    /* set the baud rate to 38400. */
    cfsetspeed(&termio, B38400);

    /* set parity to none. */
    termio.c_cflag &= ~(PARENB | PARODD);

    /* set databits to 8. */
    termio.c_cflag = ((termio.c_cflag & ~CSIZE) | CS8);

    /* set flow control to none. */
    termio.c_cflag &= ~(CRTSCTS);
    termio.c_iflag &= ~(IXON | IXOFF | IXANY);
    termio.c_cflag |= (CLOCAL | CREAD);

    /* apply our port config. */
    r = tcsetattr(fd, TCSANOW, &termio);

    if (r == -1) {
        std::cerr << "Unable to set attributes: " << port << std::endl;
        exit(-1);
    }

    return fd;
}

int init_gsm()
{
    struct termios options;

    gsm_fd = open("/dev/gsm", O_RDWR | O_NOCTTY | O_NDELAY);
    if (gsm_fd == -1) {
        perror("open_port: Unable to open /dev/gsm - ");
        return -1;
    } else {
        fcntl(gsm_fd, F_SETFL, 0);
        logger("Serial connection established on /dev/gsm\n");
    }

    // Get the current options for the port...
    tcgetattr(gsm_fd, &options);

    // Set the baud rates to whatever is needed... (Max 115200)
    cfsetispeed(&options, B115200);

    // Enable the receiver and set local mode...
    options.c_cflag |= (CLOCAL | CREAD);

    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    // Set the new options for the port...
    tcsetattr(gsm_fd, TCSANOW, &options);

    for(int i = 0; i < 4; ++i) {
        char tmp[4];
        char buff[64];
        tmp[0] = 'A';
        tmp[1] = 'T';
        tmp[2] = 0;
        write_to_gsm(tmp);
        read_from_gsm(buff);
    }

    return 0;
}
